/*------------------------------------------------------------------------------
* GeoVISTA Center, Penn State Geography Deptartment*
* Copyright (c), 2002, GeoVISTA Center
* All Rights Researved.
*************************************************************************************************
* It's the update of frank' version. It adds the support for point layer, support for line layer,
* support for layer manager, and the support for symbol rendering
*************************************************************************************************
* Original Authors: Frank Hardisty, Bonan Li
* $Author: jmacgill $
*
* $Date: 2004/12/03 19:26:34 $
*
* $Id: GeoMapExtension.java,v 1.6 2004/12/03 19:26:34 jmacgill Exp $
*
* To Do:
*
 ------------------------------------------------------------------------------*/

package edu.psu.geovista.app.map;

import edu.psu.geovista.classification.ClassifierPicker;
import edu.psu.geovista.data.geog.DataSetForApps;
import edu.psu.geovista.similarity.ClusterReadyEvent;
import edu.psu.geovista.similarity.ClusterReadyListener;
import edu.psu.geovista.symbolization.BivariateColorSchemeVisualizer;
import edu.psu.geovista.symbolization.BivariateColorSymbolClassification;
import edu.psu.geovista.symbolization.BivariateColorSymbolClassificationSimple;
import edu.psu.geovista.symbolization.ColorSymbolClassification;
import edu.psu.geovista.symbolization.event.ColorClassifierEvent;
import edu.psu.geovista.symbolization.event.ColorClassifierListener;
import edu.psu.geovista.symbollibrary.SymbolRenderingEvent;
import edu.psu.geovista.symbollibrary.SymbolRenderingListener;
import edu.psu.geovista.ui.Fisheyes;
import edu.psu.geovista.ui.cursor.GeoCursors;
import edu.psu.geovista.ui.event.*;
import edu.psu.geovista.visclass.VisualClassifier;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.net.URL;

/**
 * This class handles the user state, like selection, pan, zoom, plus
 * symbolization options.
 *
 * MapCanvas does most of the work.
 */
public class GeoMapExtension
    extends JPanel
    implements
//MouseListener, MouseMotionListener,
    ActionListener, SelectionListener, IndicationListener, DataSetListener,
    ColorClassifierListener, ColorArrayListener,
    SpatialExtentListener, ComponentListener,
    ConditioningListener, SymbolRenderingListener,ClusterReadyListener {

  public static final int VARIABLE_CHOOSER_MODE_ACTIVE = 0;
  public static final int VARIABLE_CHOOSER_MODE_FIXED = 1;
  public static final int VARIABLE_CHOOSER_MODE_HIDDEN = 2;
  transient private MapCanvasExtension mapCan;
  transient private VisualClassifier visClassOne;
  transient private VisualClassifier visClassTwo;
  transient private JToolBar mapTools;
  transient private JPanel topContent;
  transient private Cursor[] customCursors;
  transient private GeoCursors cursors;
  transient private BivariateColorSchemeVisualizer biViz;
  transient private Dimension currSize;
  transient private Fisheyes fisheyes;
  transient private Color[] clusteringColor;

  public GeoMapExtension() {
    super();

    JPanel vcPanel = new JPanel();
    vcPanel.setLayout(new BoxLayout(vcPanel, BoxLayout.Y_AXIS));
    visClassOne = new VisualClassifier();
    visClassTwo = new VisualClassifier();
    visClassOne.setAlignmentX(Component.LEFT_ALIGNMENT);
    visClassTwo.setAlignmentX(Component.LEFT_ALIGNMENT);
    visClassOne.setVariableChooserMode(
        ClassifierPicker.VARIABLE_CHOOSER_MODE_ACTIVE);
    visClassTwo.setVariableChooserMode(
        ClassifierPicker.VARIABLE_CHOOSER_MODE_ACTIVE);
    visClassOne.addActionListener(this);
    visClassOne.setOrientationInParentIsX(true);
    visClassTwo.addActionListener(this);
    visClassTwo.setOrientationInParentIsX(false);
    vcPanel.add(visClassTwo);
    vcPanel.add(visClassOne);

    JPanel legendPanel = new JPanel();
    legendPanel.setLayout(new BoxLayout(legendPanel, BoxLayout.X_AXIS));
    biViz = new BivariateColorSchemeVisualizer();
    legendPanel.add(vcPanel);
    legendPanel.add(Box.createRigidArea(new Dimension(4, 2)));
    legendPanel.add(biViz);

    topContent = new JPanel();
    topContent.setLayout(new BoxLayout(topContent, BoxLayout.Y_AXIS));
    makeToolbar();
    legendPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
    mapTools.setAlignmentX(Component.LEFT_ALIGNMENT);
    topContent.add(legendPanel);
    topContent.add(mapTools);

    //note: uncomment the line below for animation panel stuff
    //vcPanel.add(this.makeAnimationPanel());
    cursors = new GeoCursors();
    this.setCursor(cursors.getCursor(GeoCursors.CURSOR_ARROW_SELECT));

    this.setLayout(new BorderLayout());
    this.add(topContent, BorderLayout.NORTH);
    mapCan = new MapCanvasExtension();
    this.add(mapCan, BorderLayout.CENTER);
    this.mapCan.addIndicationListener(this);
    visClassOne.addColorClassifierListener(this);
    visClassTwo.addColorClassifierListener(this);
    this.addIndicationListener(biViz);

    visClassTwo.setHighColor(new Color(0, 150, 0)); //green
    this.currSize = new Dimension(this.getSize());
    this.fisheyes = new Fisheyes();
    this.fisheyes.setLensType(Fisheyes.LENS_HEMISPHERE);

    //this.colorClassifierChanged(new ColorClassifierEvent(visClassTwo,visClassTwo.getColorSymbolClassification()));
  }

  public void makeToolbar() {
    mapTools = new JToolBar();

    //Dimension prefSize = new Dimension(100,10);
    //mapTools.setMinimumSize(prefSize);
    //mapTools.setPreferredSize(prefSize);
    JButton button = null;
    Class cl = this.getClass();
    URL urlGif = null;
    Dimension buttDim = new Dimension(20, 20);

    //first button
    try {
      urlGif = cl.getResource("resources/select16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter selection mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_SELECT));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_SELECT);
      }
    });
    mapTools.add(button);

    mapTools.addSeparator();

    //second button
    try {
      urlGif = cl.getResource("resources/ZoomIn16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter zoom in mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_ZOOM_IN));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_ZOOM_IN);

        //GeoMap.this.setCursor(new Cursor(Cursor.HAND_CURSOR));
      }
    });
    mapTools.add(button);

    //third button
    try {
      urlGif = cl.getResource("resources/ZoomOut16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter zoom out mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_ZOOM_OUT);
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_ZOOM_OUT));
      }
    });
    mapTools.add(button);

    //fourth button
    try {
      urlGif = cl.getResource("resources/Home16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Zoom to full extent");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.mapCan.zoomFullExtent();
      }
    });
    mapTools.add(button);

    //fifth button
    try {
      urlGif = cl.getResource("resources/pan16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Enter pan mode");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_PAN);
      }
    });
    mapTools.add(button);
    //sixth button
    try {
      urlGif = cl.getResource("resources/excentric16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Excentric Labels");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_EXCENTRIC);
      }
    });
    mapTools.add(button);

    //seventh button
    try {
      urlGif = cl.getResource("resources/fisheye16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Fisheye Lens");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_FISHEYE);
      }
    });
    mapTools.add(button);
    //eighth button
    try {
      urlGif = cl.getResource("resources/magnifying16.gif");
      button = new JButton(new ImageIcon(urlGif));
      button.setPreferredSize(buttDim);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    button.setToolTipText("Magnifiying Lens");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        GeoMapExtension.this.setCursor(cursors.getCursor(
            GeoCursors.CURSOR_ARROW_PAN));
        GeoMapExtension.this.mapCan.setMode(MapCanvas.MODE_PAN);
      }
    });

    mapTools.add(button);

  }

  //When the classification changed (e.g., a different attribute selected)
  //this method is called to adjust the map with new classification colors.
  public void actionPerformed(ActionEvent e) {
    Object src = e.getSource();
    String command = e.getActionCommand();
    String varChangedCommand = this.visClassOne.getClassPick().
        COMMAND_SELECTED_VARIABLE_CHANGED;
    if ( (src == this.visClassOne) && command.equals(varChangedCommand)) {
      int index = visClassOne.getCurrVariableIndex();
      index++;
      this.mapCan.setCurrColorColumnX(index);
    }
    else if ( (src == this.visClassTwo) &&
             command.equals(varChangedCommand)) {
      int index = visClassTwo.getCurrVariableIndex();
      index++;
      this.mapCan.setCurrColorColumnY(index);
    }
  }

  public void selectionChanged(SelectionEvent e) {
    mapCan.selectionChanged(e);
    this.fireSelectionChanged(this.getSelectedObservations());
  }

  public void conditioningChanged(ConditioningEvent e) {
    mapCan.setConditionArray(e.getConditioning());
  }

  public void indicationChanged(IndicationEvent e) {
    Object source = e.getSource();

    if ( (source == this.mapCan) && (e.getIndication() >= 0)) {
      this.visClassOne.setIndicatedClass(e.getXClass());
      this.visClassTwo.setIndicatedClass(e.getYClass());

      //this.fireIndicationChanged(e.getIndication());
    }
    else if ( (source == this.mapCan) && (e.getIndication() < 0)) {
      this.visClassOne.setIndicatedClass( -1);
      this.visClassTwo.setIndicatedClass( -1);
      this.biViz.indicationChanged(new IndicationEvent(this, -1));

      //this.fireIndicationChanged(e.getIndication());
    }
    else {
      mapCan.indicationChanged(e);
    }
  }

  public void spatialExtentChanged(SpatialExtentEvent e) {
    mapCan.spatialExtentChanged(e);
  }

  public void colorArrayChanged(ColorArrayEvent e) {
    if (this.mapCan == null || e.getColors() == null) {
      return;
    }
    this.mapCan.setObservationColors(e.getColors());
  }

  public void colorClassifierChanged(ColorClassifierEvent e) {
    if (e.getSource() == this.visClassOne) {
      e.setOrientation(e.SOURCE_ORIENTATION_X);
    }
    if (e.getSource() == this.visClassTwo) {
      e.setOrientation(e.SOURCE_ORIENTATION_Y);
    }
    this.biViz.colorClassifierChanged(e);
    if (this.mapCan == null) {
      return;
    }

    //ColorSymbolClassification colorSymbolizer = e.getColorSymbolClassification();
    //Object src = e.getSource();
    ColorSymbolClassification colorSymbolizerX = this.visClassOne.
        getColorClasser();
    ColorSymbolClassification colorSymbolizerY = this.visClassTwo.
        getColorClasser();

    BivariateColorSymbolClassificationSimple biColorSymbolizer =
        new BivariateColorSymbolClassificationSimple();
    //turn these around to match what happens in the
    //scatterplot
    biColorSymbolizer.setClasserX(colorSymbolizerX.getClasser());
    biColorSymbolizer.setColorerX(colorSymbolizerX.getColorer());

    biColorSymbolizer.setClasserY(colorSymbolizerY.getClasser());
    biColorSymbolizer.setColorerY(colorSymbolizerY.getColorer());
    this.mapCan.setBivarColorClasser(biColorSymbolizer);
  }

  public void setXVariable(int var) {
    this.visClassOne.setCurrVariableIndex(var);
  }

  public void setYVariable(int var) {
    this.visClassTwo.setCurrVariableIndex(var);
  }

  public void setXChooserMode(int chooserMode) {
    this.visClassOne.setVariableChooserMode(chooserMode);
  }

  public void setYChooserMode(int chooserMode) {
    this.visClassTwo.setVariableChooserMode(chooserMode);
  }

  public void setBivarColorClasser(BivariateColorSymbolClassification
                                   bivarColorClasser) {
    this.mapCan.setBivarColorClasser(bivarColorClasser);
  }

  public void setSelectedObservations(int[] selObs) {
    this.mapCan.setSelectedObservationsInt(selObs);
  }

  public void setClusteringColor(Color[] clusteringColor){
    this.clusteringColor = clusteringColor;
  }

  public int[] getSelectedObservations() {
    return this.mapCan.getSelectedObservationsInt();
  }

  public Color[] getColors() {
    return this.mapCan.getColors();
  }

  public BivariateColorSymbolClassification
      getBivariateColorSymbolClassification() {
    return this.biViz.getBivariateColorClassification();
  }

  //start component listener stuff
  public void componentHidden(ComponentEvent e) {
  }

  public void componentMoved(ComponentEvent e) {
  }

  public void componentResized(ComponentEvent e) {
    //todo: zoom in or out a bit

  }

  public void componentShown(ComponentEvent e) {
  }

  //end component listener stuff

  /**
   * adds an IndicationListener
   */
  public void addIndicationListener(IndicationListener l) {
    this.mapCan.addIndicationListener(l);
  }

  /**
   * removes an IndicationListener from the component
   */
  public void removeIndicationListener(IndicationListener l) {
    this.mapCan.removeIndicationListener(l);
  }

  /**
   * adds an SelectionListener
   */
  public void addSelectionListener(SelectionListener l) {
    this.mapCan.addSelectionListener(l);
  }

  /**
   * removes an SelectionListener from the component
   */
  public void removeSelectionListener(SelectionListener l) {
    this.mapCan.removeSelectionListener(l);
  }

  /**
   * adds an SpatialExtentListener
   */
  public void addSpatialExtentListener(SpatialExtentListener l) {
    this.mapCan.addSpatialExtentListener(l);
  }

  /**
   * removes an SpatialExtentListener from the component
   */
  public void removeSpatialExtentListener(SpatialExtentListener l) {
    listenerList.remove(SpatialExtentListener.class, l);
    this.mapCan.removeSpatialExtentListener(l);
  }

  /**
   * implements ColorClassifierListener
   */
  public void addColorClassifierListener(ColorClassifierListener l) {
    this.visClassOne.addColorClassifierListener(l);
    this.visClassTwo.addColorClassifierListener(l);

  }

  /**
   * removes an ColorClassifierListener from the component
   */
  public void removeColorClassifierListener(ColorClassifierListener l) {
    this.visClassOne.removeColorClassifierListener(l);
    this.visClassTwo.removeColorClassifierListener(l);
  }

  public void setBackground(Color bg) {
    if ( (mapCan != null) && (bg != null)) {
      this.mapCan.setBackground(bg);
    }
  }

  /***
   * This is the interface for inputing data (including both spatial objects and
   * non-spatial attributes.
   */
  public void dataSetChanged(DataSetEvent e) {
    //Already has data there...this new data won't be the active layer
    //until later it is explicitly set.
    if (mapCan.getNumberOfLayers() > 0 ){
      mapCan.dataSetChanged(e);
    }else{
      mapCan.dataSetChanged(e);
      this.setDataSet(e.getDataSet());
    }
  }

  //implement the ClusterReadyListener (package: edu.psu.geovista.similarity)
  public void clusterReady(ClusterReadyEvent e){
    this.setClusteringColor(e.getObservationColors());
    this.mapCan.setClusteringColor(clusteringColor);
  }

  public void setActiveLayerIdx(int idx)
  {
    if (this.mapCan != null){
      this.mapCan.setActiveLayerIdx(idx);
      this.setDataSet(this.mapCan.getActiveLayerData());
    }
  }

  public void setDataSet(Object[] data) {
    //set default data to get color from
    DataSetForApps dataSet = new DataSetForApps();
    dataSet.setDataObject(data);

    this.setDataSet(dataSet);
  }

  private void setDataSet(DataSetForApps dataSet) {
    this.visClassOne.setData(dataSet.getDataObjectOriginal());
    this.visClassTwo.setData(dataSet.getDataObjectOriginal());

    int numNumeric = dataSet.getNumberNumericAttributes();
//    int currColorColumnX; // = this.mapCan.getCurrColorColumnX();
//    int currColorColumnY; // = this.mapCan.getCurrColorColumnY();

    if (numNumeric > 1) {
      this.setXVariable(1);
      this.mapCan.setCurrColorColumnX(1);
      this.setYVariable(2);
      this.mapCan.setCurrColorColumnY(2);
    }else if (numNumeric > 0) {
      this.setXVariable(1);
      this.mapCan.setCurrColorColumnX(1);
      this.setYVariable(1);
      this.mapCan.setCurrColorColumnY(1);

    }
  }

  public void setAuxiliarySpatialData(Object[] data) {
    mapCan.setAuxiliarySpatialData(data);
  }

  public void symbolsReady(SymbolRenderingEvent e) {
    this.mapCan.setSymbols(e.getSymbols());
  }

  /**
   * Notify all listeners that have registered interest for
   * notification on this event type. The event instance
   * is lazily created using the parameters passed into
   * the fire method.
   * @see EventListenerList
   */
  public void fireSelectionChanged(int[] newSelection) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    SelectionEvent e = null;

    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == SelectionListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new SelectionEvent(this, newSelection);
        }

        ( (SelectionListener) listeners[i + 1]).selectionChanged(e);
      }
    } //next i
  }

}